package org.third.security.crypto;

import static org.third.security.crypto.CryptoAlgorithm.parseByteToHexStr;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class AsymmetricEncryption {
    static final Logger logger = LoggerFactory.getLogger(AsymmetricEncryption.class);
    public static final String plainText = "(plain Text )";

    public static final String password = "pass-word";

    public static final byte[] encryptAsAsymmetric(String content, java.security.Key publicKey, CryptoAlgorithm algorithm) {
        try {
            Cipher cipher = Cipher.getInstance(algorithm.getName());

            // 用公钥加密
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(content.getBytes("UTF-8"));

            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static final byte[] decryptAsAsymmetric(byte[] content, java.security.Key privateKey, CryptoAlgorithm algorithm) {
        try {
            Cipher cipher = Cipher.getInstance(algorithm.getName());
            // 用私钥去解密
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 得到解密后的结果
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Test
    // 不仅可以使用DSA算法，同样也可以使用RSA算法做数字签名
    /**
     * <pre>
     * RSA:
     *    这种算法1978年就出现了，它是第一个既能用于数据加密也能用于数字签名的算法。它易于理解和操作，也很流行。
     *    算法的名字以发明者的名字命名：RonRivest, AdiShamir 和Leonard Adleman。
     * RSA同时有两把钥匙，
     *   公钥与私钥。同时支持数字签名。数字签名的意义在于，对传输过来的数据进行校验。确保数据在传输工程中不被修改
     * </pre>
     * 
     * public static final String KEY_ALGORITHM = "RSA"; public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
     */
    public void testRSA() throws Exception {
        testAsymmetric(CryptoAlgorithm.RSA);
    }

    @Test
    /**
     * DSA-Digital Signature Algorithm 是Schnorr和ElGamal签名算法的变种，被美国NIST作为DSS(DigitalSignature Standard)。
     * 简单的说，这是一种更高级的验证方式，用作数字签名。不单单只有公钥、私钥，还有数字签名。私钥加密生成数字签名，公钥验证数据及签名。 如果数据和签名不匹配则认为验证失败！即 传输中的数据
     * 可以不再加密，接收方获得数据后，拿到公钥与签名 验证数据是否有效
     */
    public void testDSA() throws Exception {
        // testAsymmetric(CryptoAlgorithm.DSA);
        String content = "protected data";

        ////////  Generate the Key pair
        String KEY_ALGORITHM = CryptoAlgorithm.DSA.getName();
        String SIGNATURE_ALGORITHM = CryptoAlgorithm.DSA.getName();
        String DEFAULT_SEED = "$%^*%^()(HJG8awfasdfasdfjas7"; // 默认种子

        java.security.KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        java.security.SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(DEFAULT_SEED.getBytes());
        // Modulus size must range from 512 to 1024 and be a multiple of 64
        keygen.initialize(640, secureRandom);

        java.security.KeyPair keys = keygen.genKeyPair();
        java.security.PrivateKey privateKeyOriginal = keys.getPrivate();
        java.security.PublicKey publicKeyOriginal = keys.getPublic();

        System.out.println("私钥format：" + privateKeyOriginal.getFormat());
        System.out.println("公钥format：" + publicKeyOriginal.getFormat());

        /////////////////////// 产生签名
        System.out.println("用私钥对信息进行数字签名");
        // 1) geneate the private Key
        byte[] keyBytesPrivate = privateKeyOriginal.getEncoded();
        System.out.println(keyBytesPrivate);

        java.security.spec.PKCS8EncodedKeySpec privatekeySpecPkcs8 = new PKCS8EncodedKeySpec(keyBytesPrivate);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKeyDecoded = factory.generatePrivate(privatekeySpecPkcs8);// 生成 私钥

        // 用privateKey对信息进行数字签名
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKeyDecoded);
        signature.update(content.getBytes("UTF-8"));
        byte[] signedData = signature.sign();

        // 用publicKey验证签名
        byte[] keyBytesPublic = publicKeyOriginal.getEncoded();
        java.security.spec.X509EncodedKeySpec publicKeySpecX509 = new X509EncodedKeySpec(keyBytesPublic);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKeyDecoded = keyFactory.generatePublic(publicKeySpecX509);

        Signature signature3 = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature3.initVerify(publicKeyDecoded);

        String contentNew = "aaa";
        signature3.update(contentNew.getBytes("UTF-8"));
        System.out.println("经验证 数据和签名匹配:" + signature3.verify(signedData)); // false

        signature3.update(content.getBytes("UTF-8"));
        System.out.println("经验证 数据和签名匹配:" + signature3.verify(signedData));// true

    }

    @Test
    public void testDiffieHellman() throws Exception {
        // testAsymmetric(CryptoAlgorithm.DiffieHellman);
    }

    private void testAsymmetric(CryptoAlgorithm algorithm) throws Exception {
        logger.info("test" + algorithm + " started");
        // 实例化Key
        java.security.KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm.getName());

        // 初始化密钥对生成器，密钥大小为1024位
        keyPairGenerator.initialize(1024);
        // 获取一对钥匙
        java.security.KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获得公钥
        java.security.Key publicKey = keyPair.getPublic();
        // 获得私钥
        java.security.Key privateKey = keyPair.getPrivate();

        String content = "protected data";

        // 加密
        System.out.println("加密前：" + content);
        byte[] encryptResult = encryptAsAsymmetric(content, publicKey, algorithm);//
        String encryptResultStr = parseByteToHexStr(encryptResult);
        System.out.println("加密后：" + encryptResultStr);

        // 解密
        // byte[] encryptResult = parseHexStr2Byte(encryptResultStr);
        byte[] decryptResult = decryptAsAsymmetric(encryptResult, privateKey, algorithm);
        System.out.println("解密后：" + new String(decryptResult));

        logger.info("test" + algorithm + " end");
    }

    private byte[] encryptAsPBEWithMD5AndDES(String content, String password) throws Exception {
        // 使用PBEWithMD5AndDES算法获取Cipher实例
        Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");

        // 基于密码生成 初始化密钥（secret key）
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        KeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKey secretKey = keyFactory.generateSecret(keySpec);

        // 初始化Cipher为加密器
        byte[] salt = new byte[] {};
        int iterationCount = 1000;
        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, iterationCount);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);

        // 对数据进行加密
        byte[] encryptedData = cipher.doFinal(content.getBytes());

        // 输出加密后的内容
        return encryptedData;

    }

    private byte[] decryptAsPBEWithMD5AndDES(byte[] content, String password) throws Exception {

        // 使用PBEWithMD5AndDES算法获取Cipher实例
        Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
        // 初始化密钥
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        KeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKey secretKey = keyFactory.generateSecret(keySpec);

        // 初始化Cipher为解密器
        byte[] salt = new byte[] {};
        int iterationCount = 1000;
        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, iterationCount);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);

        // 获得解密后的数据
        byte[] decryptedData = cipher.doFinal(content);

        return decryptedData;
    }
}
